package com.zy.gpdemo.domain;

import com.alibaba.fastjson.JSON;
import com.yc.utils.files.PropertiesUtil;
import com.zy.gpdemo.dao.HsqldbDao;
import com.zy.gpdemo.print.PrintHandle;
import com.zy.gpdemo.service.ESCPrinter;
import com.zy.gpdemo.service.PrinterService;

import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Printer {

	private String ip; // 打印机IP
	private String name; // 打印机库区名称
	private ESCPrinter esc; // ESC打印机实例
	private boolean offline = true; // 是否离线
	private int total; // 记录本次打印任务总计数量
	private int current; // 记录当前打印序号
	private boolean run; // 运行状态
	private List<Integer> printHistory = new ArrayList<>(Arrays.asList(0, 0, 0, 0, 0, 0, 0)); // 最近几天的打印数量
	private final List<String> messageIds = new ArrayList<>();
	private final BlockingQueue<PrintTask> currentTask = new LinkedBlockingQueue<>(); // 当前打印任务队列
	private final BlockingQueue<PrintTask> awaitTask = new LinkedBlockingQueue<>(); // 等待打印任务队列
	private final List<PrintTask> historyTask = new ArrayList<>(); // 历史打印任务
	private final List<String> otherIp = new ArrayList<>(); // 需要执行其他库区打印任务的库区IP
	private final List<BlockingQueue<PrintTask>> other = new CopyOnWriteArrayList<>(); // 引用其他库区的等待打印任务队列
	private final Lock lock = new ReentrantLock(); // 可重入锁，用于在执行其他库区任务时，避免被移除，导致空指针异常
	private int failure = 0; // 打印失败次数
	private Thread thread; // 打印线程
	private int maxPrintAmount=50;	// 单次最大打印数量,默认为50张
	private int printTimeIntervalOfTwoPage=2;	// 打印两张拣货单的时间间隔,默认是2s
	private int printTimeIntervalOfTwoTask=15;	// 打印两轮拣货单的时间间隔,默认是15s
	private HsqldbDao hsqldbDao;
	private String appRoot;
	private String stockCode;
	private int failTime=3;	// 拣货单打印失败最大次数,默认是3次

	public Printer(String ip, String name, String appRoot, String stockCode) {
		this.ip = ip;
		this.name = name;
		this.appRoot = appRoot;
		this.stockCode = stockCode;
		esc = new ESCPrinter(ip, 9100);
		try {
			maxPrintAmount = Integer.parseInt(PropertiesUtil.getPropertiesKey("config/application.properties", "autoPrint.maxPrintAmount"));
			printTimeIntervalOfTwoPage = Integer.parseInt(PropertiesUtil.getPropertiesKey("config/application.properties",
					"autoPrint.printTimeIntervalOfTwoPage"));
			printTimeIntervalOfTwoTask = Integer.parseInt(PropertiesUtil.getPropertiesKey("config/application.properties",
					"autoPrint.printTimeIntervalOfTwoTask"));
			failTime = Integer.parseInt(PropertiesUtil.getPropertiesKey("config/application.properties",
					"autoPrint.failTime"));
		}catch(Exception e) {
			System.out.println("加载配置文件失败");
		}
		
	}

	public String getAppRoot() {
		return appRoot;
	}

	public void setAppRoot(String appRoot) {
		this.appRoot = appRoot;
	}

	public String getStockCode() {
		return stockCode;
	}

	public void setStockCode(String stockCode) {
		this.stockCode = stockCode;
	}

	public String getIp() {
		return ip;
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public ESCPrinter getEsc() {
		return esc;
	}

	public void setEsc(ESCPrinter esc) {
		this.esc = esc;
	}

	public boolean isOffline() {
		offline = !esc.Connect();
		return offline;
	}

	public List<String> getOtherIp() {
		return otherIp;
	}

	public void setOffline(boolean offline) {
		this.offline = offline;
	}

	public int getTotal() {
		return total;
	}

	public void setTotal(int total) {
		this.total = total;
	}

	public int getCurrent() {
		return current;
	}

	public void setCurrent(int current) {
		this.current = current;
	}

	public boolean isRun() {
		return run;
	}

	public void setRun(boolean run) {
		this.run = run;
	}

	public BlockingQueue<PrintTask> getCurrentTask() {
		return currentTask;
	}

	public BlockingQueue<PrintTask> getAwaitTask() {
		return awaitTask;
	}

	public List<PrintTask> getHistoryTask() {
		return historyTask;
	}

	public List<Integer> getPrintHistory() {
		return printHistory;
	}

	/**
	 * 初始化打印机对象
	 * 加载近7天的打印数量
	 * 加载最近100个历史打印任务
	 * @param hsqldbDao
	 */
	public void init(HsqldbDao hsqldbDao) {
		// 传递hsqldbDao对象
		this.hsqldbDao = hsqldbDao;
		List<String> date = utilDate(-7);
		for (int i = 0; i < date.size() && i < printHistory.size(); i++) {
			printHistory.set(i, hsqldbDao.findTaskNum(-1, name, stockCode, date.get(i)));
		}
		List<PrintTask> history = hsqldbDao.findHistory(name, stockCode, 0, 100);
		for (PrintTask printTask : history) {
			historyTask.add(printTask);
		}
	}

	/**
	 * 获取最近day天的日期
	 * 
	 * @param day
	 * @return List<String>
	 */
	private static List<String> utilDate(int day) {
		SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
		Calendar calendar = Calendar.getInstance();
		List<String> list = new ArrayList<>();
		calendar.add(Calendar.DAY_OF_MONTH, day);
		while (day++ < 0) {
			calendar.add(Calendar.DAY_OF_MONTH, 1);
			list.add(simpleDateFormat.format(calendar.getTime()));
		}
		return list;
	}

	/**
	 * 添加其他库区打印任务
	 * 
	 * @param queue
	 */
	public void addOther(BlockingQueue<PrintTask> queue) {
		try {
			if (lock.tryLock(2000L, TimeUnit.MILLISECONDS)) {
				other.add(queue);
				lock.unlock();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 移除其他库区打印任务
	 * 
	 * @param queue
	 */
	public void removeOther(BlockingQueue<PrintTask> queue) {
		try {
			// 尝试获取锁，避免移除该库区任务和执行该库区任务冲突
			if (lock.tryLock(2000L, TimeUnit.MILLISECONDS)) {
				other.remove(queue);
				lock.unlock();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 打印线程方法
	 */
	public void printJob() {
		// 记录线程执行状态
		run = true;
		PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
		while (run &&  !offline && !esc.getStatus().isOffLine()) {
			// 更新打印数据
			if (currentTask.size() == 0) {
				if (awaitTask.size() != 0) {
					// 每次最多打印maxPrintAmount张拣货单
					PrintTask basePrintTaskData = awaitTask.peek();
					for (int i = 0; i < maxPrintAmount && basePrintTaskData != null; i++) {
						currentTask.add(basePrintTaskData);
						awaitTask.poll();
						messageIds.add(basePrintTaskData.getMessageId());
						hsqldbDao.updateTask(1, new Timestamp(System.currentTimeMillis()), basePrintTaskData.getId());
						basePrintTaskData = awaitTask.peek();
					}
					// 重置打印数量和当前打印序号
					total = currentTask.size();
					current = 0;
				}
			}
			// 更新前端展示数据
			PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
			// 开始打印
			if (currentTask.size() != 0 && printNumber()) { // 判断打印机是否正常，并打印本次打印数量
				// 依次打印队列里面的任务
				while (current < total) {
					PrintTask taskData = currentTask.peek();

					if (taskData != null) {
						String failMsg = print(taskData.getMessageId(), taskData.getAppRoot(), this.name + "-" + this.stockCode,
								taskData.getReportName(), taskData.getReportParams(), taskData.getPrintNums());
						if(!"true".equals(failMsg)){
							// 打印机脱机或者离线，记录当前拣货单单号
							System.out.println("打印机失败，记录当前顺序: " + total + " : " + current);
							if(++failure > failTime) {
								PrinterService.updateFailTimes(taskData.getAppRoot(), Long.parseLong(taskData.getMessageId()), failMsg);
								currentTask.poll();
								current++;
								Timestamp timestamp = new Timestamp(System.currentTimeMillis());
								taskData.setLastTime(timestamp);
								try {
									hsqldbDao.updateTask(-2, timestamp, taskData.getId());
								} catch (Exception e) {
									System.out.println("数据库更新打印失败任务" + taskData.getId() + "状态异常");
								}
								failure = 0;
							}else {
								sleep(10);
							}
						}else {
							failure = 0;
							Timestamp timestamp = new Timestamp(System.currentTimeMillis());
							taskData.setLastTime(timestamp);
							historyTask.add(0, taskData);
							if (historyTask.size() > 200) {
								historyTask.clear();
								List<PrintTask> history = hsqldbDao.findHistory(name, stockCode, 0, 100);
								for (PrintTask printTask : history) {
									historyTask.add(printTask);
								}
							}
							try {
								hsqldbDao.updateTask(-1, timestamp, taskData.getId());
							} catch (Exception e) {
								System.out.println("数据库更新" + taskData.getId() + "状态异常");
							}
							current++;
							printHistory.set(6, printHistory.get(6) + 1);
							sleep(printTimeIntervalOfTwoPage);
						}
					}
					if (!run) {
						break;
					}
				}
				PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
			}

			if (!run) {
				break;
			}

			// 等待time秒后继续执行打印
			sleep(printTimeIntervalOfTwoTask);
			PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
			// 检测是否有其他库区打印任务需要打印,加锁避免产生修改other长度
			if (lock.tryLock() && currentTask.size() == 0) {
				try {
					int length = other.size();
					for (int i = 0; i < length; i++) {
						BlockingQueue<PrintTask> queue = other.get(i);
						if (queue.size() != 0) {
							PrintTask basePrintTaskData = queue.poll();
							for (int j = 0; j < 50 && basePrintTaskData != null; j++) {
								currentTask.add(Objects.requireNonNull(basePrintTaskData));
								messageIds.add(basePrintTaskData.getMessageId());
								basePrintTaskData = queue.poll();
								hsqldbDao.updateTask(1, new Timestamp(System.currentTimeMillis()),
										basePrintTaskData.getId());
							}
							total = currentTask.size();
							current = 0;
							// 更新打印机任务状态
							for (Printer printer : PrinterService.printers) {
								if (printer.getIp().equals(otherIp.get(i))) {
									PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(printer)));
								}
							}
							break;
						}
					}
				}catch(Exception e) {
					System.out.println("平台编码:" + name + "\t任务区间:" + stockCode+ "\t打印机IP:" + ip);
					System.out.println("【ERROR】添加其他库区打印任务异常");
				}finally {
					lock.unlock();
				}
			}
			List<String> date = utilDate(-7);
			for (int i = 0; i < date.size() && i < printHistory.size(); i++) {
				printHistory.set(i, hsqldbDao.findTaskNum(-1, name, stockCode, date.get(i)));
			}
		}
		// 任务中断，将当前打印队列里面的打印任务添加到等待打印的队列里面
		while(currentTask.size()!=0) {
			awaitTask.add(currentTask.poll());
		}
		
		run = false;
		
		// 更新打印机状态
		PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
	}

	/**
	 * 打印需要打印的任务数量
	 * 
	 * @return boolean
	 */
	public boolean printNumber() {
		if (!check()) {
			return false;
		}
		String msg = ip + "打印数量  " + total + " : " + current + "\r\n";

		for (int i = 0; i < messageIds.size(); i++) {
			msg += total + " : " + (i + 1) + "\t" + messageIds.get(i) + "\r\n";
		}
		if (esc.printNumber(msg)) {
			// 发送打印任务成功，等待1秒检查打印机状态
			sleep(1);
			return check();
		} else {
			// 发送打印任务失败
			return false;
		}
	}

	/**
	 * 检查打印机状态
	 * 
	 * @return boolean
	 */
	public boolean check() {
		// 测试打印机是否在线
		if (!esc.Connect()) {
			offline = true;
			PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
			return false;
		} else {
			offline = false;
			PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
		}
		// 获取网络打印机状态
		if (!esc.PrinterStatus()) {
			System.out.println("读取打印机状态失败");
			return false;
		}
		// 判断打印机状态
		if (esc.status.isOffLine()) {
			System.out.println("打印机脱机");
			return false;
		} else {
			return true;
		}
	}

	/**
	 * 启动打印机打印线程
	 */
	public void start() {
		// 判断打印机状态
		if (isOffline() || esc.status.isOffLine()) {
			return;
		}
		// 判断线程状态
		if (thread == null || !thread.isAlive() || thread.getState() == Thread.State.TERMINATED) {
			thread = new Thread(this::printJob);
			thread.start();
		}
	}

	/**
	 * 终止打印机打印线程
	 */
	public void stop() {
		if (thread.isAlive()) {
			thread.interrupt();
		}
		run = false;
	}

	/**
	 * 打印拣货单方法
	 * 
	 * @param params
	 * @param appRoot
	 * @param printServiceName
	 * @param reportName
	 * @param reportParams
	 * @param printNums
	 * @return 打印结果
	 */
	private String print(String params, String appRoot, String printServiceName, String reportName,
			String reportParams, int printNums) {
		// 执行打印
		if (!check()) {
			return "false";
		}
		esc.printMessage(ip + "打印数量  " + total + ":" + (current + 1));
		// 打印数据
		String msg = PrintHandle.printHandle(params, appRoot, printServiceName, reportName, reportParams, printNums);
		if (!"ok".equals(msg)) {
			System.out.println("打印任务发送打印机失败");
			return msg;
		}
		// 执行打印任务,等待2秒
		sleep(2);
		// 更新打印机状态，打印任务执行后打印机若脱机或者打印机离线等故障判断此次打印任务失败
		if (!check()) {
			return "false";
		}
		// 更新打印任务
		PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
		// 更新当前打印任务队列
		currentTask.poll();
		messageIds.remove(params);
		return "true";
	}

	/**
	 * 通过拣货单的messageId重新打印拣货单
	 * 将拣货单信息重新加入到等待打印任务队列里面
	 * 
	 * @param msgId
	 * @return boolean
	 */
	public boolean rePrint(String msgId) {
		for (int i = 0; i < historyTask.size(); i++) {
			PrintTask basePrintTaskData = historyTask.get(i);
			String id = basePrintTaskData.getMessageId();
			if (id.equals(msgId)) {
				try {
					PrintTask printTask = new PrintTask(basePrintTaskData);
					hsqldbDao.insertTask(printTask);
					awaitTask.offer(printTask, 2, TimeUnit.SECONDS);
					PrinterService.sendMessage(1, this.ip, JSON.parseObject(JSON.toJSONString(this)));
					return true;
				} catch (InterruptedException e) {
					return false;
				}
			}
		}
		return false;
	}

	/**
	 * 封装sleep函数,当程序被中断时修改run信号量
	 * 
	 * @param time	等待时间(单位:s)
	 */
	private void sleep(int time) {
		try {
			Thread.sleep(time * 1000);
		} catch (InterruptedException e) {
			run = false;
		}
	}

}
